package com.dhj.yygh.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.dhj.yygh.enums.PaymentTypeEnum;
import com.dhj.yygh.enums.RefundStatusEnum;
import com.dhj.yygh.model.order.OrderInfo;
import com.dhj.yygh.model.order.PaymentInfo;
import com.dhj.yygh.model.order.RefundInfo;
import com.dhj.yygh.service.OrderInfoService;
import com.dhj.yygh.service.PayInfoService;
import com.dhj.yygh.service.RefundService;
import com.dhj.yygh.service.WeixinService;
import com.dhj.yygh.utils.ConstantWXPayPropertiesUtils;
import com.dhj.yygh.utils.HttpClient;
import com.github.wxpay.sdk.WXPayConstants;
import com.github.wxpay.sdk.WXPayUtil;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class WeixinServiceImpl implements WeixinService {

    @Autowired
    OrderInfoService orderInfoService;

    @Autowired
    PayInfoService payInfoService;

    @Autowired
    RedisTemplate redisTemplate;

    @Autowired
    RefundService refundService;

    /**
     * 根据订单 id 使用微信支付得到该订单的支付相关信息
     *
     * @param orderId 订单 id
     * @return 返回微信支付的相关信息
     */
    @Override
    public Map<String, Object> createNative(Long orderId) {

        try {
            // 如果 redis 中有数据，直接返回
            Map redisMap = (Map) redisTemplate.opsForValue().get(orderId.toString());

            if (redisMap != null) {
                return redisMap;
            }

            // 得到订单的相关信息
            OrderInfo orderInfo = orderInfoService.getById(orderId.toString());

            // 先向支付记录表中添加一次相关信息(也可以在完成支付后统一添加支付记录)
            payInfoService.savePaymentInfo(orderInfo, PaymentTypeEnum.WEIXIN.getStatus());

            /*
            设置参数，调用微信方生成二维码的接口
            */
            // 将参数转换为 xml 格式，使用商户 key 进行加密
            // 设置参数
            Map<String, String> paramMap = new HashMap<>();
            paramMap.put("appid", ConstantWXPayPropertiesUtils.APPID);// 商户 id
            paramMap.put("mch_id", ConstantWXPayPropertiesUtils.PARTNER);// 商户号
            paramMap.put("nonce_str", WXPayUtil.generateNonceStr());// 唯一的随机字符串
            String body = new DateTime(orderInfo.getReserveDate()).toString("yyyy-MM-dd HH:mm") + "就诊" + orderInfo.getDepname();// 主体信息
            paramMap.put("body", body);// 设置主体信息
            paramMap.put("out_trade_no", orderInfo.getOutTradeNo()); // 订单编号
            paramMap.put("total_fee", "1");// 订单金额(1 代表 0.01 元，这里为了方便测试)
            paramMap.put("spbill_create_ip", "127.0.0.1");// 当前 ip 地址
            paramMap.put("notify_url", "http://guli.shop/api/order/weixinPay/weixinNotify");// 回调地址
            paramMap.put("trade_type", "NATIVE");// 支付类型

            /*
            调用微信生成支付二维码的接口
            */
            // 使用 HttpClient 调用微信支付接口(接口地址固定)
            HttpClient httpClient = new HttpClient("https://api.mch.weixin.qq.com/pay/unifiedorder");

            /*
             设置 Map 里面的参数
             参数 1 需要设置的参数的 Map<String,String> 集合
             参数 2 商户 key ，由微信官方提供给认证过的商户
             */
            httpClient.setXmlParam(WXPayUtil.generateSignedXml(paramMap, ConstantWXPayPropertiesUtils.PARTNERKEY));

            httpClient.setHttps(true);// 设置其支持 https 协议

            httpClient.post(); // 发送 post 请求

            // 返回相关数据(xml 格式)
            String resultXml = httpClient.getContent();
            Map<String, String> map = WXPayUtil.xmlToMap(resultXml);// 使用微信提供的 api 将 Xml 转化为 Map 方便传递到前端

            // 4、封装返回结果集
            Map<String, Object> resultMap = new HashMap<>();
            resultMap.put("orderId", orderId);
            resultMap.put("totalFee", orderInfo.getAmount());
            resultMap.put("resultCode", map.get("result_code"));
            resultMap.put("codeUrl", map.get("code_url"));// 二维码的地址
            log.info((String) resultMap.get("codeUrl"));

            // 如果微信支付的信息获取成功
            if (null != resultMap.get("resultCode")) {
                // 将微信支付的相关信息保存到 redis 中，orderId 为 key 设置过期时间 2 小时
                redisTemplate.opsForValue().set(orderId.toString(), resultMap, 2, TimeUnit.HOURS);
            }

            return resultMap;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 查询微信支付的状态
     *
     * @param orderId 订单 id
     * @return 返回支付状态信息的 Map 集合
     */
    @Override
    public Map<String, String> getPayStatus(Long orderId) {
        try {
            // 根据 orderId 获取订单信息
            OrderInfo orderInfo = orderInfoService.getById(orderId);

            // 封装提交参数
            Map paramMap = new HashMap();
            paramMap.put("appid", ConstantWXPayPropertiesUtils.APPID);
            paramMap.put("mch_id", ConstantWXPayPropertiesUtils.PARTNER);
            paramMap.put("out_trade_no", orderInfo.getOutTradeNo());// 商户单号由商户自己生成
            paramMap.put("nonce_str", WXPayUtil.generateNonceStr());

            // 设置请求的内容 & 发送请求
            HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/pay/orderquery");
            client.setXmlParam(WXPayUtil.generateSignedXml(paramMap, ConstantWXPayPropertiesUtils.PARTNERKEY));
            client.setHttps(true);
            client.post();

            // 得到微信返回的数据( xml 字符串 )
            String xmlStr = client.getContent();

            // 把微信返回的 xml 数据转化为 Map 返回
            return WXPayUtil.xmlToMap(xmlStr);

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /***
     * 退款
     * @param orderId
     * @return
     */
    @Override
    public Boolean refund(Long orderId) {
        // 查询支付信息
        PaymentInfo paymentInfo =
                payInfoService.getPaymentInfo(orderId, PaymentTypeEnum.WEIXIN.getStatus());

        // 将支付记录中的相关信息加入退款记录中
        RefundInfo refundInfo = refundService.saveRefundInfo(paymentInfo);

        // 判断当前退款记录中是否已经标识为退款，若已退款，return true
        if (refundInfo.getRefundStatus().intValue() == RefundStatusEnum.REFUND.getStatus()) {
            return true;// 代表已经退款
        }

        // 封装调用微信接口需要的参数
        Map<String, String> paramMap = new HashMap<>();

        try {
            paramMap.put("appid", ConstantWXPayPropertiesUtils.APPID);       // 公众账号ID
            paramMap.put("mch_id", ConstantWXPayPropertiesUtils.PARTNER);   // 商户编号
            paramMap.put("nonce_str", WXPayUtil.generateNonceStr());
            paramMap.put("transaction_id", paymentInfo.getTradeNo()); // 微信订单号
            paramMap.put("out_trade_no", paymentInfo.getOutTradeNo()); // 商户订单编号
            paramMap.put("out_refund_no", "tk" + paymentInfo.getOutTradeNo()); // 商户退款单号
            paramMap.put("total_fee", "1");// 总金额
            paramMap.put("refund_fee", "1"); // 退款的金额
            String paramXml = WXPayUtil.generateSignedXml(paramMap, ConstantWXPayPropertiesUtils.PARTNERKEY);// 将参数转化为 xml 字符串
            HttpClient client = new HttpClient("https://api.mch.weixin.qq.com/secapi/pay/refund"); // 设置调用微信的接口

            // 封装所需参数
            client.setXmlParam(paramXml);
            client.setHttps(true);

            // 设置退款的证书,(必须，此处获取证书的路径封装到了 HttpClient 内部)
            client.setCert(true);
            client.setCertPassword(ConstantWXPayPropertiesUtils.PARTNER);// 商户 id
            client.post();

            // 微信方返回数据
            String xml = client.getContent();
            Map<String, String> resultMap = WXPayUtil.xmlToMap(xml);

            // 若状态码不为空且标识为成功，则退款成功
            if (null != resultMap && WXPayConstants.SUCCESS.equalsIgnoreCase(resultMap.get("result_code"))) {

                // 更新数据库中的退款记录
                refundInfo.setCallbackTime(new Date());
                refundInfo.setTradeNo(resultMap.get("refund_id"));
                refundInfo.setRefundStatus(RefundStatusEnum.REFUND.getStatus());
                refundInfo.setCallbackContent(JSONObject.toJSONString(resultMap));
                refundService.updateById(refundInfo);
                return true;
            }
            return false;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
}
